#pragma once
#include <zGraphicsConfig.hpp>
#include "../../Renderer/Renderer.hpp"
#include <common.hpp>
#include <Utility/Tools.hpp>

/*
ATTENTION:
use begin() FIRST
then setUniform
otherwise INVALID_OPERATION occurs
*/

namespace zzz{
class ZGRAPHICS_CLASS GLShaderObject : public Uncopyable {
  friend class Shader;

public:
  typedef enum {VERT,FRAG,GEOM} ShaderType;
  GLShaderObject(ShaderType type);
  virtual ~GLShaderObject();

  bool LoadFromFile(const char * filename);
  bool LoadFromMemory(const char * program);

  bool Compile(char *source);
  char* GetCompilerError(void);
  GLint GetAttribLocation(char* attribName);

  ShaderType progratype_;
  GLuint ShaderObject;
  bool is_compiled;
};

class ZGRAPHICS_CLASS GLVertexShader : public GLShaderObject {
public:
  GLVertexShader();
};

class ZGRAPHICS_CLASS GLFragmentShader : public GLShaderObject {
public:
  GLFragmentShader();
};

class ZGRAPHICS_CLASS GLGeometryShader : public GLShaderObject {
public:
  GLGeometryShader();
};

struct UniformParam {
  enum {F1,F2,F3,F4,I1,I2,I3,I4,M2,M3,M4,OBJROT,OBJROTTRANS,ENVROT,ENVROTTRANS} type;
  string name;
  union {
    float f1;
    float f2[2];
    float f3[3];
    float f4[4];
    int i1;
    int i2[2];
    int i3[3];
    int i4[4];
    float m2[4];
    float m3[9];
    float m4[16];
  };
  bool operator==(const string &str)const {return name==str;}
};

class ZGRAPHICS_CLASS Shader : public Uncopyable {
public:
  Shader();
  Shader(const char *vertfile, const char *fragfile, const char *geomfile);
  Shader(GLVertexShader *verts, GLFragmentShader *frags, GLGeometryShader *geoms);
  virtual ~Shader();
  bool AddShader(GLShaderObject* ShaderProgram); //!< add a Vertex or Fragment Program

  bool LoadVertexFile(const char *file);
  bool LoadVertexMemory(const char *mem);
  bool LoadFragmentFile(const char *file);
  bool LoadFragmentMemory(const char *mem);
  bool LoadGeometryFile(const char *file);
  bool LoadGeometryMemory(const char *mem);

  bool Link(void);                               //!< Link all Shaders
  char* GetLinkerError(void);                      //!< get Linker messages

  void Begin();                                  //!< use Shader. OpenGL calls will go through shader.
  static void End();                                    //!< Stop using this shader. OpenGL calls will go through regular pipeline.

  // Uniform Variables
  bool SetUniform1f(char* varname, GLfloat v0);  //!< set float uniform to program
  bool SetUniform2f(char* varname, GLfloat v0, GLfloat v1); //!< set vec2 uniform to program
  bool SetUniform3f(char* varname, GLfloat v0, GLfloat v1, GLfloat v2); //!< set vec3 uniform to program
  bool SetUniform4f(char* varname, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); //!< set vec4 uniform to program

  bool SetUniform1i(char* varname, GLint v0);
  bool SetUniform2i(char* varname, GLint v0, GLint v1);
  bool SetUniform3i(char* varname, GLint v0, GLint v1, GLint v2);
  bool SetUniform4i(char* varname, GLint v0, GLint v1, GLint v2, GLint v3);

  bool SetUniform1fv(char* varname, GLsizei count, const GLfloat *value);
  bool SetUniform2fv(char* varname, GLsizei count, const GLfloat *value);
  bool SetUniform3fv(char* varname, GLsizei count, const GLfloat *value);
  bool SetUniform4fv(char* varname, GLsizei count, const GLfloat *value);
  bool SetUniform1iv(char* varname, GLsizei count, const GLint *value);
  bool SetUniform2iv(char* varname, GLsizei count, const GLint *value);
  bool SetUniform3iv(char* varname, GLsizei count, const GLint *value);
  bool SetUniform4iv(char* varname, GLsizei count, const GLint *value);

  bool SetUniformMatrix2fv(char* varname, GLsizei count, GLboolean transpose, const GLfloat *value);
  bool SetUniformMatrix3fv(char* varname, GLsizei count, GLboolean transpose, const GLfloat *value);
  bool SetUniformMatrix4fv(char* varname, GLsizei count, GLboolean transpose, const GLfloat *value);

  // Receive Uniform variables:
  void GetUniformfv(char* name, GLfloat* values);
  void GetUniformiv(char* name, GLint* values);

  // Vertex Attributes, use in glVertex(...) method
  bool SetVertexAttrib1s(GLuint index, GLshort v0);
  bool SetVertexAttrib2s(GLuint index, GLshort v0, GLshort v1);
  bool SetVertexAttrib3s(GLuint index, GLshort v0, GLshort v1, GLshort v2);
  bool SetVertexAttrib4s(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3);
  bool SetVertexAttrib1f(GLuint index, GLfloat v0);
  bool SetVertexAttrib2f(GLuint index, GLfloat v0, GLfloat v1);
  bool SetVertexAttrib3f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2);
  bool SetVertexAttrib4f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
  bool SetVertexAttrib1d(GLuint index, GLdouble v0);
  bool SetVertexAttrib2d(GLuint index, GLdouble v0, GLdouble v1);
  bool SetVertexAttrib3d(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2);
  bool SetVertexAttrib4d(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3);

  vector <UniformParam> UniformParamList;
  bool Clear();
  bool SetUniforms(Renderer *currenderer=NULL);
  bool ChangeUniform(const string &name,GLint i1);
  bool ChangeUniform(const string &name,GLfloat f1);
  bool Init();

  GLint GetUniLoc(const GLchar *name);        // get location of a variable
  GLint GetAttLoc(const GLchar *name);

  GLuint ProgramObject;             // GLProgramObject
  bool is_linked;

  GLShaderObject *vert,*frag,*geom;
  bool vert_hold,frag_hold,geohold_;
};

}